﻿#region Legal

// Jimmy Zimmerman
// Team Mongoose
//
// END USER LICENSE AGREEMENT
// IMPORTANT - READ THIS CAREFULLY:  This End User License Agreement is a legal agreement
// between you (either an individual, business entity, currently active identity of an
// individual with Multiple Personality Disorder, octopus overlord, or otherworldly entity),
// and Team Mongoose, for the enclosed, components.
//
// By reading this document and/or installing this product, you agree to be bound by the
// terms of this EULA.
//
// Team Mongoose owns all copyright, trade secret, trademark, trade wars,
// patent, portent, and potential rights to these components.  Team Mongoose
// grants you the right to deploy the enclosed components.
//
// If you agree to the terms of this EULA, a license to use these components is granted to you.
//
// If you should happen to benefit from the enclosed components, then you are legally and
// contractually bound to thank us for it. Send your regards to jimmyzimms@hotmail.com.
//
// OTHER RIGHTS AND LIMITATONS
// You may not reverse-engineer, decompile, decode, disassemble, psycho-analyze, or otherwise
// attempt to find hidden meanings between the lines of this EULA; unless, of course, you should
// happen to find some, and they are funny.
//
// You may not translate this EULA or any part of the components into Ancient Sumerian.
//
// THESE COMPONENTS ARE PROVIDED “AS-IS” WITHOUT WARRANTY OF ANY KIND. ANY USE OF THE COMPONENTS
// CONTAINED IS AT YOUR OWN RISK. TEAM MONGOOSE DISCLAIM ALL WARRANTIES, EITHER
// EXPRESS OR IMPLIED, WITH RESPECT TO THE ACCURRACY AND CORRECTNESS OF THE COMPONENTS CONTAINED
// HEREIN. TEAM MONGOOSE DOES NOT WARRANT THAT THE COMPONENTS ARE FLAWLESS.
//
// REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS, WITH OR WITHOUT MODIFICATION, ARE PERMITTED
// PROVIDED THAT THE FOLLOWING CONDITIONS ARE MET:
// * REDISTRIBUTIONS OF SOURCE CODE MUST RETAIN THE ABOVE COPYRIGHT NOTICE
// * REDISTRIBUTIONS IN BINARY FORM MUST NOTE THE USE OF THE COMPONENT IN DOCUMENTATION AND/OR
//   OTHER MATERIALS PROVIDED WITH THE DISTRIBUTION.
// * NEITHER THE NAME OF TEAM MONGOOSE MAY BE USED TO ENDORES OR PROMOTE PRODUCTS
//   DERIVED FROM THIS SOFTWARE WITHOUT SPECIFIC PRIOR WRITTEN PERMISSION.
//
// IN NO EVENT SHALL TEAM MONGOOSE BE HELD LIABLE FOR INCIDENTAL, SPECIAL, INDIRECT,
// INCONSEQUENTIAL, UNBELIEVABLE, EXAGGERATED, VERBOSE, OR TYPICAL DAMAGES INCURRED WHILE USING
// THE ENCLOSED COMPONENTS.
//
// OUR STUFF ALWAYS WORKS - SOMETIMES.

#endregion

using Microsoft.Devices.Sensors;
using Microsoft.Phone.Reactive;
using Microsoft.Xna.Framework;
using Roller.ViewModels;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Linq;
using Roller.Animation;
using DiceRoller.Dice;
using Roller.Collision;

namespace Roller
{
    public partial class MainPage
    {
        #region Fields

        private Accelerometer accelerometer;
        private Rectangle viewPort;

        private bool useEmulation = true;
        private bool diceRolled = false;

        private Timer updateTimer;

        private int animationCount;
        // make the roll last ~2 seconds
        private int animationDuration = 60;

        #endregion

        #region Methods

        // Load data for the ViewModel Items
        private static void MainPageLoaded( object sender, RoutedEventArgs e )
        {
            if ( !App.ViewModel.IsDataLoaded )
            {
                App.ViewModel.LoadData();
            }
        }

        // Hhhmmm....forgot that there was no commanding support yet.  Faking and baking it.
        private void ButtonClick( object sender, RoutedEventArgs e )
        {
            var button = sender as Button;
            var context = ( RollingViewModel ) this.DataContext;

            context.AddDice( (string) button.Tag );

            this.RollingCanvas.Children.Add( new RenderedDie( Convert.ToInt32( button.Tag ) ) );
        }

        private void StartAccelerometerEmulation()
        {
            // Use ToObservable to convert the IEnumerable sequence returned 
            // from EmulateAccelerometerReading to an IObservable sequence
            var emulationDataAsObservable = EmulateAccelerometerReading().ToObservable();

            // Use the subscribe method to subscribe. The InvokeAccelerometerReadingChanged
            // handler will be called whenever new Vector3 objects are produced by the emulator
            emulationDataAsObservable.Subscribe( args => InvokeAccelerometerReadingChanged( args ) );
        }

        private static IEnumerable<Vector3> EmulateAccelerometerReading()
        {
            // Create a random number generator
            var random = new Random();

            // Loop indefinitely
            for ( double theta = 0; ; theta += .1 )
            {
                // Generate a Vector3 in which the values of each axes slowly drift between -1 and 1 and
                // then normalize the vector
                var reading = new Vector3( (float) Math.Sin( theta ), (float) Math.Cos( theta * 1.1 ), (float) Math.Sin( theta * .7 ) );
                reading.Normalize();

                // At random intervals, generate a random spike in the data
                if ( random.NextDouble() > .95 )
                {
                    reading = new Vector3( (float) ( random.NextDouble() * 3.0 - 1.5 ),
                     (float) ( random.NextDouble() * 3.0 - 1.5 ),
                     (float) ( random.NextDouble() * 3.0 - 1.5 ) );
                }

                // return the vector and then sleep
                yield return reading;
                Thread.Sleep( 10000 );
            }
        }

        private void InvokeAccelerometerReadingChanged( Vector3 data )
        {
            // Call an event handler on the page's thread
            Deployment.Current.Dispatcher.BeginInvoke( () => AccelerometerReadingChanged( data ) );

            //if ( !this.diceRolled )
                //this.updateTimer
        }

        private void AccelerometerReadingChanged( Vector3 data )
        {
            // Output the data to a TextBlock on the screen
            this.AccelerometerReading.Text = "x: " + data.X.ToString( "0.00" ) + " y: " + data.Y.ToString( "0.00" ) + " z: " + data.Z.ToString( "0.00" );
            
            var context = (RollingViewModel) this.DataContext;
            context.RollDice( string.Empty );
        }

        private void SetAccelerometerReadingLevel()
        {
            if ( !useEmulation )
            {
                // If emulation is not being used, instantiate the AccelerometerSensor
                accelerometer = new Accelerometer();

                // Obtain an Observable sequence using the FromEvent method
                var accelerometerReadingAsObservable =
                    Observable.FromEvent<AccelerometerReadingEventArgs>(
                    ev => accelerometer.ReadingChanged += ev,
                    ev => accelerometer.ReadingChanged -= ev );

                // Use from and select to create a stream of Vector3 from AccelerometerReadingEventArgs stream 
                var vector3FromAccelerometerEventArgs = from args in accelerometerReadingAsObservable
                                                        select new Vector3( (float) args.EventArgs.X, (float) args.EventArgs.Y, (float) args.EventArgs.Z );


                // Subscribe to the Observable sequence of Vector3 objects
                // InvokeAccelerometerReadingChanged will be called whenever a new Vector3 is available in the stream
                vector3FromAccelerometerEventArgs.Subscribe( args => InvokeAccelerometerReadingChanged( args ) );

                // Start the accelerometer
                try
                {
                    accelerometer.Start();
                }
                catch ( AccelerometerFailedException ex )
                {
                    this.AccelerometerReading.Text = "error starting accelerometer";
                }
            }
            else
            {
                // Otherwise, start a new thread for emulation
                // Removing for the time being.  Utilizing a basic button to roll.
                //var emulationThread = new Thread( StartAccelerometerEmulation );
                //emulationThread.Start();
            }
        }

        /// <summary>
        /// Manual method for rolling dice.  Debug only, removing for production.
        /// </summary>
        /// <param name="sender">An <see cref="object"/> representing the sender.</param>
        /// <param name="e">The <see cref="RoutedEventArgs"/> for the instance.</param>
        private void RollDice( object sender, RoutedEventArgs e )
        {
            var random = new Random( DateTime.Now.Millisecond );
            if ( this.viewPort.Width <= 0 )
            {
                this.viewPort.Width = Convert.ToInt32( this.RollingCanvas.ActualWidth );
                this.viewPort.Height = Convert.ToInt32( this.RollingCanvas.ActualHeight );
            }

            this.RollingCanvas.Children.ToList().ForEach( c =>
                {
                    var die = (RenderedDie) c;

                    if ( die != null )
                    {
                        die.Roll();
                    }
                } );

            this.animationCount = 0;
        }

        #region Animation 

        /// <summary>
        /// Main method for cylcing through the children collection of the dice Canvas and updating the position of each based off
        /// of their velocity.
        /// </summary>
        private void Update()
        {
            if ( this.RollingCanvas.Children.Count == 0 )
            {
                this.animationCount = this.animationDuration;
                return;
            }

            if ( this.animationCount <= this.animationDuration )
            {
                this.RollingCanvas.Children.ToList().ForEach( c =>
                {
                    var die = (RenderedDie) c;

                    if ( die != null )
                    {
                        die.Velocity = DetectCollision.ContainsObject( this.viewPort, die );
                        die.Update();
                    }
                } );

                ++this.animationCount;
            }
        }

        #endregion

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of the MainPage object, sets the DataContext to the App.ViewModel (default behavior apparently), 
        /// and wires up a MainPageLoaded event / method.
        /// </summary>
        public MainPage()
        {
            InitializeComponent();

            // Set the data context of the listbox control to the sample data
            //DataContext = App.ViewModel;
            this.DataContext = new RollingViewModel();
            this.Loaded += new RoutedEventHandler( MainPageLoaded );

            // Instantiate the Accelerometer reading method.  Static based off of the bool private field ATM.
            this.SetAccelerometerReadingLevel();

            // Used for animation.
            this.updateTimer = new Timer( ( obj ) =>  Deployment.Current.Dispatcher.BeginInvoke( () => Update() ),
                new AutoResetEvent( true ), 1000, 33 );
        }

        #endregion
    }
}